package com.timk.goserver.server.util;

import com.timk.goserver.server.model.ServerUserInfo;

/**
 * This is an implementation of the EGF rating system described at
 * http://gemma.ujf.cas.cz/~cieply/GO//gor.html
 * @author TKington
 *
 */
public class RatingManager {
	private static int [] con = 
			{0, 116, 110, 105, 100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 51,
			47, 43, 39, 35, 31, 27, 24, 21, 18, 15, 13, 11, 10};
	
	private static double sizeOfChange(double rating) {
		if(rating >= 2700) {
			return 10;
		}
		
		int slot = (int)(rating / 100);
		int lowVal = con[slot];
		int highVal = con[slot + 1];
		
		double fraction = (rating % 100) / 100;
		return lowVal + fraction * (highVal - lowVal);
	}
	
	private static double inconsistency(double rating) {
		if(rating > 2700) {
			return 70;
		}
		
		return 205 - (rating / 20);
	}
	
	private static double winProb(double rating, double delta) {
		double a = inconsistency(rating);
		return 1.0 / (Math.pow(Math.E, delta / a) + 1.0);
	}
	
	/**
	 * Updates the players' ratings
	 * @param black the black player
	 * @param white the white player
	 * @param handicap the handicap
	 * @param komi the komi
	 * @param blackWon true if Black won
	 */
	public static void updateRatings(ServerUserInfo black, ServerUserInfo white,
			double handicap, double komi, boolean blackWon) {
		
		int blackRating = black.getEloRating();
		int whiteRating = white.getEloRating();
		
		int blackRatingWithHandicap;
		if(handicap > 0) {
			blackRatingWithHandicap = (int)(blackRating 
					+ (100 * (handicap - 0.5))
					- (50 * ((komi - 0.5) / 6)));
		} else {
			blackRatingWithHandicap = blackRating;
		}
		
		double winProbBlack;
		double winProbWhite;
		if(blackRating < whiteRating) {
			winProbBlack = winProb(blackRatingWithHandicap,
					whiteRating - blackRatingWithHandicap);
			winProbWhite = 1.0 - winProbBlack;
		} else {
			winProbWhite = winProb(whiteRating,
					blackRatingWithHandicap - whiteRating);
			winProbBlack = 1.0 - winProbWhite;
		}
		
		double blackDelta;
		double whiteDelta;
		if(blackWon) {
			blackDelta = sizeOfChange(blackRating) * (1 - winProbBlack);
			whiteDelta = sizeOfChange(whiteRating) * -winProbWhite;
		} else {
			blackDelta = sizeOfChange(blackRating) * -winProbBlack;
			whiteDelta = sizeOfChange(whiteRating) * (1 - winProbWhite);
		}
		
		int newBlackRating = (int)(blackRating + Math.round(blackDelta));
		if(newBlackRating < 100) {
			newBlackRating = 100;
		}
		black.updateRating(newBlackRating);
		
		int newWhiteRating = (int)(whiteRating + Math.round(whiteDelta));
		if(newWhiteRating < 100) {
			newWhiteRating = 100;
		}
		white.updateRating(newWhiteRating);
	}
	
	/**
	 * Converts a rank and grade into a rating
	 * @param rank numeric rank
	 * @param grade 'k' or 'd'
	 * @return a rating, or -1 if unsuccessful
	 */
	public static int stringToRating(int rank, String grade) {
		try {
			switch(grade.charAt(0)) {
			case 'k':
				if(rank < 1 || rank > 30) {
					return -1;
				}
				return Math.max(100, 2100 - 100 * rank);
			case 'd':
				if(rank < 1 || rank > 9) {
					return -1;
				}
				return 2000 + 100 * rank;
			default:
				return -1;
			}
		}
		catch(NumberFormatException e) {
			return -1;
		}
	}
	
	/**
	 * Converts a rating to a string
	 * @param rating
	 * @return a string like 5 kyu (+23%)
	 */
	public static String ratingToString(int rating) {
		int num;
		String type;
		int percent;
		if(rating < 2050) {
			num = 21 - rating / 100;
			type = " kyu";
			percent = rating % 100;
			if(percent > 49) {
				percent -= 100;
				num--;
			}
		} else {
			num = (rating - 2000) / 100;
			type = " dan";
			percent = rating % 100;
			if(percent > 49) {
				percent -= 100;
				num++;
			}
		}
		
		StringBuffer buf = new StringBuffer(20);
		buf.append(num).append(type).append(" (");
		
		if(percent >= 0) {
			buf.append('+');
		}
		
		buf.append(percent).append("%)");
		return buf.toString();
	}
}
